package fr.univ.lr.icone.zone;

import java.awt.Point;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Iterator;

import fr.univ.lr.icone.agents.Agent;
import fr.univ.lr.icone.configuration.Configuration;
import fr.univ.lr.icone.gui.GraphicUI;
import fr.univ.lr.icone.gui.event.Event;
import fr.univ.lr.icone.gui.event.EventType;
import fr.univ.lr.icone.log.Log;
import fr.univ.lr.icone.trace.Trace;

/**
 * Class Zone extends Thread
 * 
 * //inplementé Cette classe s'occupe de la symulation (correspind au
 * controlleur dasn le modele vu/controlleur). Elle contien une classe
 * Configuration qui sera utilise pour récuperer une ArrayList d'Agents qui
 * sera crae a partir du fichier de configuration XML. Elle cré des Event
 * qu'elle envoie a GraphicUI qui correspendent a tous les agent crée
 * 
 * //pas encore implémente Elle se chargera aussi de deplacer les agentset de
 * les faire communiquer. Elle informera la classe GrapheUI (pour l'informer des
 * positions et opignon des agents)grace a un system d'event De meme pour la
 * classe Log pour qu'elle puisse enregistrer la trace de la configuration.
 * 
 * 
 * 
 **/
public class Zone extends Thread {
	// VARIABLES DE CLASSES -------------------
	boolean simuler, pause, canSimulate;

	// liste des agents
	public ArrayList<Agent> agents;

	// taille plateau
	int gridSize;

	// pointeur vers l'GraphicUI
	GraphicUI ihm;

	// Configuraton
	Configuration config;

	// Trace
	Trace trace;

	// Log
	Log log;
	private Runnable logRunnable;

	// CONSTRUCTEURS ---------------------------
	/**
	 * constructeur prend en parametre une GraphicUI et le nom du fichier de
	 * configuration
	 * 
	 * @param logName
	 * 
	 */
	public Zone(GraphicUI ihm, String nameConfig, String traceName,
			String logName) {
		simuler = false;
		pause = false;
		canSimulate = false;
		config = new Configuration(nameConfig);
		this.ihm = ihm;
		trace = new Trace(traceName, true);
		trace.start();
		log = new Log(logName);
	}

	// METHODES PUBLIC------------------------------------

	/**
	 * 
	 */
	public void run() {
		System.out.println("Zone.run() ... START");
		init();
		this.canSimulate = true;

		Thread th = new Thread(logRunnable = new Runnable() {
			@Override
			public void run() {
				while (true) {
					try {
						log.log(agents);
						Thread.sleep(20000);
					} catch (IOException e) {
						e.printStackTrace();
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}
			}
		});
		th.start();

		while (true) {
			while (pause) {
				boolean toto = true;
			}
			for (int i = 0; i < agents.size(); i++) {
				randomMoveAgent(agents.get(i));
			}
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}

	/**
	 * retourne la liste des agents
	 * 
	 * @return
	 */

	public ArrayList<Agent> getAgents() {
		return this.agents;
	}

	public boolean isSimulable() {
		return canSimulate;
	}

	/**
	 * Ajoute un agent et en informe le system
	 * 
	 * @param a
	 *            agent a ajouter
	 * @return true si l'agent a ete ajoute
	 */

	public boolean addAgent(Agent a) {
		boolean r = false;
		if (isFree(a.getX(), a.getY())) {
			agents.add(a);
			// motify le system de la creation d'un nouvelle agent
			notify(agentToEvent(a, EventType.AGENT_CREATION));
			r = true;
		}
		return r;
	}

	public void restart() {
		// TODO
	}

	public void stopSimulation() {
		this.simuler = false;
	}

	public void setPause(boolean b) {
		this.pause = b;
	}

	// METHODES INTERNES---------------------------------------

	/**
	 * methode init Charge le fichier de de configuration passer en parametre du
	 * constructeur de Zonz(2ene parametre) et recupere la liste des agents
	 */
	private void init() {

		config.loadConfiguration();
		this.agents = config.getAgentList();
		gridSize = config.getGridSize();

		for (int i = 0; i < agents.size(); i++) {
			// on crer un event de type Creation d'agent puis on l'envoi
			notify(agentToEvent(agents.get(i), EventType.AGENT_CREATION));
		}
	}

	/**
	 * Permet de communiquer avec un agent proche
	 * 
	 * @param a
	 */
	private void communicateToNeighbor(Agent a) {
		int i = 0;
		boolean c = true;
		while (i < agents.size() && c == true) {
			if (communicate(a, agents.get(i))) {
				c = false;
			}
		}
	}

	/**
	 * Permet de faire communiquer 2 agents
	 * 
	 * @param a1
	 *            agent 1
	 * @param a2
	 *            agent 2
	 */
	private boolean communicate(Agent a1, Agent a2) {
		// si ils peuvent communiquer & qu'il sont sufisament proche

		// si leurs indice de confiance et leurs opignons permette de changer
		// opignon
		// a1 donne son opignon a a2
		// desactiver a1 et a2

		return false;

	}

	/**
	 * Parcourt la liste des agents et les deplace de maniere aleatoire si
	 * possible
	 */
	@SuppressWarnings("unused")
	private void randomMoveMultipleAgent() {
		for (int i = 0; i < agents.size(); i++) {
			randomMoveAgent(agents.get(i));
		}
	}

	/**
	 * Deplace un agent de facon aleatoire en fonction de son pas
	 * 
	 * @param a
	 *            agent a deplacer
	 */
	private void randomMoveAgent(Agent a) {

		int pas = a.getStep();
		int x = a.getX();
		int y = a.getY();

		ArrayList<Point> points = new ArrayList<Point>();

		if (isFree(x + pas, y) && ((x + pas) < gridSize)) {
			points.add(new Point(x + pas, y));
		}
		if (isFree(x - pas, y) && ((x - pas) > 0)) {
			points.add(new Point(x - pas, y));
		}
		if (isFree(x, y + pas) && ((y + pas) < gridSize)) {
			points.add(new Point(x, y + pas));
		}
		if (isFree(x, y - pas) && ((x - pas) > 0)) {
			points.add(new Point(x, y - pas));
		}

		int tt = (int) (Math.random() * points.size());

		if (points.size() > 0) {
			notify(agentToEvent(a, EventType.POSITION_CHANGED, points.get(tt)));
			a.setX(points.get(tt).x);
			a.setY(points.get(tt).y);
		}

	}

	/**
	 * Permet de verifier si il n'ypa pas d'agent a une position
	 * 
	 * @param x
	 *            position en x
	 * @param y
	 *            position en y
	 * @return true si il n'y a pas d'agent a cette position
	 */
	private boolean isFree(int x, int y) {
		boolean r = true;

		for (Iterator<Agent> it = agents.iterator(); it.hasNext();) {
			Agent aTmp = it.next();
			if (aTmp.getX() == x && aTmp.getY() == y) {
				r = false;
			}
		}

		return r;
	}

	/**
	 * Done une nouvelle position a un agent si il n'y a pas d'agent deja
	 * present a la nouvelle position
	 * 
	 * @param a
	 *            agent a deplacer
	 * @param x
	 *            position en x
	 * @param y
	 *            position en y
	 * @return true si le deplacement a reussi
	 */
	@SuppressWarnings("unused")
	private boolean moveAgent(Agent a, int x, int y) {
		boolean r = false;
		if (isFree(x, y)) {
			a.setX(x);
			a.setY(y);
			r = true;
		}
		return r;
	}

	/**
	 * Envoi un event a l'ihm et au log
	 * 
	 * @param e
	 *            event en envoye
	 */
	private void notify(Event e) {
		// a l'ihm
		ihm.receiveEvent(e);
		// a la trace
		trace.receiveEvent(e);
	}

	/**
	 * Cre un event correspondant a un agent
	 * 
	 * @param a
	 *            agent qui sert de base a l'agent
	 * @return
	 */
	@SuppressWarnings("unused")
	private Event agentToEvent(Agent a) {
		return new Event(a.getId(), EventType.NONE, a.getOpinion(), new Point(a
				.getX(), a.getY()), null);
	}

	/**
	 * Cre un event correspondant a un agent
	 * 
	 * @param a
	 *            agent qui sert de base a l'agent
	 * @param eType
	 *            type de l'event
	 * @return event cree
	 */
	private Event agentToEvent(Agent a, EventType eType) {
		return new Event(a.getId(), eType, a.getOpinion(), new Point(a.getX(),
				a.getY()), null);
	}

	/**
	 * Cre un event correspondant a un agent
	 * 
	 * @param a
	 *            agent qui sert de base a l'agent
	 * @param eType
	 *            type de l'event
	 * @param oldX
	 *            ancienne position en X
	 * @param oldY
	 *            ancienne position en Y
	 * @return event cree
	 */
	@SuppressWarnings("unused")
	private Event agentToEvent(Agent a, EventType eType, int oldX, int oldY) {
		return new Event(a.getId(), eType, a.getOpinion(), new Point(a.getX(),
				a.getY()), new Point(oldX, oldY));
	}

	/**
	 * Cre un event correspondant a un agent
	 * 
	 * @param a
	 *            agent qui sert de base a l'agent
	 * @param eType
	 *            type de l'event
	 * @param old
	 *            ancienne position
	 * @return event cree
	 */
	private Event agentToEvent(Agent a, EventType eType, Point newPos) {
		return new Event(a.getId(), eType, a.getOpinion(), newPos, new Point(a
				.getX(), a.getY()));
	}

	/*
	 * AMELIORATION POSSIBLE:
	 * 
	 * utiliser une matrice pour gerer la position des agents (+ plus rapide ,
	 * -plus compliqué)
	 */
}
